<?php

require_once dirname(__FILE__) . '/../../Domain51UnitTest.config.php';
require_once 'Domain51/Model/Abstract.php';
require_once 'Domain51/Model/Mapper.php';

class Domain51_Model_AbstractTestable extends Domain51_Model_Abstract { }
class Domain51_Model_WrongMetaType { }

class Domain51_Model_AbstractTest extends PHPUnit_Framework_TestCase
{
    private $_meta = null;
    
    public function setUp()
    {
        // mock "init" method so it doesn't try to check config values
        $this->_meta = $this->getMock(
            'Domain51_Model_Meta_Abstract', 
            array('_init'),
            array('MockModel')
        );
        $this->_meta->columns = array('id', 'column');
        
    }
    
    public function tearDown()
    {
        
    }
    
    public function testLoadsPropertiesFromPropertiesValueInConfigArray()
    {
        $config = array(
            'properties' => array('id' => rand(10, 20), 'column' => 'string'),
            'meta' => $this->_meta
        );
        
        $model = new Domain51_Model_AbstractTestable($config);
        foreach ($config['properties'] as $key => $value) {
            $this->assertTrue(isset($model->$key));
            $this->assertSame($value, $model->$key);
        }
    }
    
    public function testAllowsSettingOfProperties()
    {
        $config = array(
            'meta' => $this->_meta
        );
        
        $model = new Domain51_Model_AbstractTestable($config);
        
        $properties = array('id' => rand(10, 20), 'column' => 'string');
        foreach ($properties as $key => $value) {
            $this->assertFalse(isset($model->$key));
            $model->$key = $value;
            $this->assertTrue(isset($model->$key));
            $this->assertSame($value, $model->$key);
        }
    }
    
    public function testCanRevertAllChanges()
    {
        $config = array(
            'properties' => array('id' => rand(10, 20), 'column' => 'string'),
            'meta' => $this->_meta
        );
        
        $model = new Domain51_Model_AbstractTestable($config);
        foreach ($config['properties'] as $key => $value) {
            $this->assertTrue(isset($model->$key));
            $this->assertSame($value, $model->$key);
        }
        
        $second_properties = array('id' => rand(10, 20), 'column' => 'second_string');
        foreach ($second_properties as $key => $value) {
            $model->$key = $value;
            $this->assertSame($value, $model->$key);
        }
        
        $model->revert();
        foreach ($config['properties'] as $key => $value) {
            $this->assertTrue(isset($model->$key));
            $this->assertSame($value, $model->$key);
        }
    }
    
    public function testProvidesModifiedStatusViaIsmodified()
    {
        $config = array(
            'properties' => array('id' => rand(10, 20), 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        $this->assertFalse($model->isModified());
        $model->id = rand(20, 30);
        $this->assertTrue($model->isModified());
    }
    
    public function testProvidesModifiedStatusOnIndividualPropertiesViaIsmodified()
    {
        $config = array(
            'properties' => array('id' => rand(10, 20), 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        $this->assertFalse($model->isModified('id'));
        $this->assertFalse($model->isModified('column'));
        
        $model->id = rand(20, 30);
        $this->assertTrue($model->isModified('id'));
        $this->assertFalse($model->isModified('column'));
    }
    
    public function testCanBeInstantiatedWithoutAnyProperties()
    {
        $config = array('meta' => $this->_meta);
        $model = new Domain51_Model_AbstractTestable($config);
    }
    
    public function testProvidesNewStatusViaIsnew()
    {
        $config = array(
            'properties' => array('id' => rand(10, 20), 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        $this->assertFalse($model->isNew());
        unset($model);
        unset($config);
        
        $config = array('meta' => $this->_meta);
        $model = new Domain51_Model_AbstractTestable($config);
        $this->assertTrue($model->isNew());
    }
    
    public function testUnsettingPropertiesRevertsThemBackToOriginalState()
    {
        $originalId = rand(10, 20);
        $config = array(
            'properties' => array('id' => $originalId, 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        $this->assertSame($originalId, $model->id, "Sanity Check");
        
        $newId = rand(30, 40);
        $model->id = $newId;
        
        unset($model->id);
        
        $this->assertNotSame($newId, $model->id);
        $this->assertSame($originalId, $model->id);
    }
    
    public function testUnsetOnNonChangedPropertyHasNoEffect()
    {
        $originalId = rand(10, 20);
        $config = array(
            'properties' => array('id' => $originalId, 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        $this->assertSame($originalId, $model->id, "Sanity Check");
        
        unset($model->id);
        $this->assertSame($originalId, $model->id, "Should not have changed");
    }
    
    public function testAttachedCallbackIsCalledOnSave()
    {
        $config = array(
            'properties' => array('id' => rand(10, 20), 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        // dirty "id" so isModified() is tripped
        $model->id = rand(100, 200);
        
        $callback = $this->getMock(
            'Domain51_Model_Mapper', 
            array_merge(
                get_class_methods('Domain51_Model_Mapper'),
                array('_init')
            ),
            array('MockMapper')
        );
        $callback->expects($this->once())
            ->method('save');
        
        $model->attach(array($callback, 'save'));
        $model->save();
    }
    
    public function testUnsetDoesNotRevertAfterSave()
    {
        $originalId = rand(10, 20);
        $config = array(
            'properties' => array('id' => $originalId, 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        $this->assertSame($originalId, $model->id, "Sanity Check");
        
        $newId = rand(30, 40);
        $model->id = $newId;
        $model->save();
        unset($model->id);
        
        // verify the values are correct now
        $this->assertSame($newId, $model->id);
        $this->assertNotSame($originalId, $model->id);
    }
    
    public function testCallingSaveChangesIsModified()
    {
        $originalId = rand(10, 20);
        $config = array(
            'properties' => array('id' => $originalId, 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        $this->assertSame($originalId, $model->id, "Sanity Check");
        
        $newId = rand(30, 40);
        $model->id = $newId;
        
        $this->assertTrue($model->isModified('id'), "Sanity Check");
        $model->save();
        $this->assertFalse($model->isModified('id'));
        
        // verify the values are correct now
        $this->assertSame($newId, $model->id);
        $this->assertNotSame($originalId, $model->id);
    }
    
    public function testSaveUsesResultsOfCallbackAsChangedValues()
    {
        $originalId = rand(10, 20);
        $config = array(
            'properties' => array('id' => $originalId, 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        // dirty "id" to trip isModified()
        $model->id = $originalId;
                
        $callback = $this->getMock(
            'Domain51_Model_Mapper', 
            array_merge(
                get_class_methods('Domain51_Model_Mapper'),
                array('_init')
            ),
            array('MockMapper')
        );
        
        $callback->expects($this->once())
            ->method('save')
            ->will($this->returnValue(array('id' => 'modified:' .$originalId)));
                
        $model->attach(array($callback, 'save'));
        $model->save();
        
        $this->assertSame('modified:' . $originalId, $model->id);
    }
    
    public function testSaveOnlyExecutesCallbackWhenIsmodifiedIsTrue()
    {
         $config = array(
            'properties' => array('id' => rand(10, 20), 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        
        $callback = $this->getMock(
            'Domain51_Model_Mapper', 
            array_merge(
                get_class_methods('Domain51_Model_Mapper'),
                array('_init')
            ),
            array('MockMapper')
        );
        $callback->expects($this->never())
            ->method('save');
        
        $model->attach(array($callback, 'save'));
        $model->save();
    }
    
    public function testToarrayReturnsLatestOfAllValues()
    {
        $originalId = rand(10, 20);
        $config = array(
            'properties' => array('id' => $originalId, 'column' => 'string'),
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        $this->assertSame($config['properties'], $model->toArray());
        
        
         $newId = rand(30, 40);
        $model->id = $newId;
        
        $this->assertTrue($model->isModified('id'), "Sanity Check");
        
        $this->assertSame(
            array('id' => $newId, 'column' => 'string'),
            $model->toArray()
        );

    }
    
    public function testThrowsExceptionWhenAnAttachingAnInvalidCallback()
    {
        $config = array(
            'meta' => $this->_meta
        );
        
        $callback = array($this, 'invalid_and_unknown_method');
        
        $model = new Domain51_Model_AbstractTestable($config);
        try {
            $model->attach($callback);
            $this->fail('Exception not caught');
        } catch (Solar_Exception $e) {
            $this->assertSame('ERR_INVALID_CALLBACK', $e->getCode());
            $this->assertSame(array($callback), $e->getInfo());
        }
    }
    
    public function testThrowsExceptionWhenTryingToSetUnknownProperty()
    {
        $config = array(
            'meta' => $this->_meta
        );
        $model = new Domain51_Model_AbstractTestable($config);
        try {
            $model->unknown = true;
            $this->fail('Exception not caught');
        } catch (Exception $e) {
            $this->assertSame('ERR_UNKNOWN_PROPERTY', $e->getCode());
            $this->assertSame(array('unknown'), $e->getInfo());
        }
    }
    
    public function testThrowsExceptionWhenNoMetaPassedIn() 
    {
        try {
            $model = new Domain51_Model_AbstractTestable();
            $this->fail();
        } catch (Exception $e) {
            $this->assertSame(
                'ERR_MISSING_CONFIG_VALUE',
                $e->getCode()
            );
            
            $this->assertSame(
                array('meta'),
                $e->getInfo()
            );
        }
    }
    
    public function testThrowsExceptionWhenWrongMetaPassedIn() 
    {
        try {
            $config = array('meta' => new Domain51_Model_WrongMetaType());
            $model = new Domain51_Model_AbstractTestable($config);
            $this->fail('Exception not caught');
        } catch (Exception $e) {
            $this->assertSame('ERR_WRONG_CONFIG_TYPE', $e->getCode());
            $this->assertSame(array('meta'), $e->getInfo());
        }
    }
}

?>